/*
 * @(#)FilePolicyModule.java
 *
 * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistribution of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 * 
 *   2. Redistribution in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */

package org.jboss.security.xacml.sunxacml.support.finder;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.security.xacml.sunxacml.AbstractPolicy;
import org.jboss.security.xacml.sunxacml.EvaluationCtx;
import org.jboss.security.xacml.sunxacml.ParsingException;
import org.jboss.security.xacml.sunxacml.finder.PolicyFinder;
import org.jboss.security.xacml.sunxacml.finder.PolicyFinderModule;
import org.jboss.security.xacml.sunxacml.finder.PolicyFinderResult;

/**
 * This module represents a collection of files containing polices,
 * each of which will be searched through when trying to find a
 * policy that is applicable to a specific request. It does not support
 * policy references.
 * <p>
 * Note that this class used to be provided in the
 * <code>com.sun.xacml.finder.impl</code> package with a warning that it
 * would move out of the core packages eventually. This is partly because
 * this class doesn't represent standard functionality, and partly because
 * it isn't designed to be generally useful as anything more than an
 * example. Because so many people have used this class, however, it stayed
 * in place until the 2.0 release.
 * <p>
 * As of the 2.0 release, you may still use this class (in its new location),
 * but you are encouraged to migrate to the new support modules that are
 * much richer and designed for general-purpose use. Also, note that the
 * <code>loadPolicy</code> methods that used to be available from this class
 * have been removed. That functionality has been replaced by the much more
 * useful <code>PolicyReader</code> class. If you need to load policies
 * directly, you should consider that new class.
 *
 * @since 1.0
 * @author Seth Proctor
 */
@SuppressWarnings({ "unchecked", "rawtypes"})
public class FilePolicyModule extends PolicyFinderModule
{

   // the schema file we're using, if any
   private File schemaFile = null;

   // the filenames for the files we'll load
   private Set fileNames;

   // the actual loaded policies
   private PolicyCollection policies;

   // the logger we'll use for all messages
   private static final Logger logger = Logger.getLogger(FilePolicyModule.class.getName());

   /**
    * Constructor which retrieves the schema file to validate policies against
    * from the <code>PolicyReader.POLICY_SCHEMA_PROPERTY</code>. If the
    * retrieved property is null, then no schema validation will occur.
    */
   public FilePolicyModule()
   {
      fileNames = new HashSet();
      policies = new PolicyCollection();

      String schemaName = System.getProperty(PolicyReader.POLICY_SCHEMA_PROPERTY);

      if (schemaName != null)
         schemaFile = new File(schemaName);
   }

   /**
    * Constructor that uses the specified <code>File</code> as the schema
    * file for XML validation. If schema validation is not desired, a null
    * value should be used.
    *
    * @param schemaFile the schema file to validate policies against,
    *                   or null if schema validation is not desired.
    */
   public FilePolicyModule(File schemaFile)
   {
      fileNames = new HashSet();
      policies = new PolicyCollection();

      this.schemaFile = schemaFile;
   }

   /**
    * Constructor that uses the specified <code>String</code> as the schema
    * file for XML validation. If schema validation is not desired, a null
    * value should be used.
    *
    * @param schemaFile the schema file to validate policies against,
    *                   or null if schema validation is not desired.
    */
   public FilePolicyModule(String schemaFile)
   {
      this((schemaFile != null) ? new File(schemaFile) : null);
   }

   /**
    * Constructor that specifies a set of initial policy files to use. This
    * retrieves the schema file to validate policies against from the
    * <code>PolicyReader.POLICY_SCHEMA_PROPERTY</code>. If the retrieved
    * property is null, then no schema validation will occur.
    *
    * @param fileNames a <code>List</code> of <code>String</code>s that
    *                  identify policy files
    */
   public FilePolicyModule(List fileNames)
   {
      this();

      if (fileNames != null)
         this.fileNames.addAll(fileNames);
   }

   /**
    * Constructor that specifies a set of initial policy files to use and
    * the schema file used to validate the policies. If schema validation is
    * not desired, a null value should be used.
    *
    * @param fileNames a <code>List</code> of <code>String</code>s that
    *                  identify policy files
    * @param schemaFile the schema file to validate policies against,
    *                   or null if schema validation is not desired.
    */
   public FilePolicyModule(List fileNames, String schemaFile)
   {
      this(schemaFile);

      if (fileNames != null)
         this.fileNames.addAll(fileNames);
   }

   /**
    * Adds a file (containing a policy) to the collection of filenames
    * associated with this module. Note that this doesn't actually load the
    * policy file. Policies aren't loaded from their files until the
    * module is initialized through the <code>init</code> method (which
    * is called automatically by the <code>PolicyFinder</code> when the
    * system comes up).
    *
    * @param filename the file to add to this module's collection of files
    */
   public boolean addPolicy(String filename)
   {
      return fileNames.add(filename);
   }

   /**
    * Indicates whether this module supports finding policies based on
    * a request (target matching). Since this module does support
    * finding policies based on requests, it returns true.
    *
    * @return true, since finding policies based on requests is supported
    */
   public boolean isRequestSupported()
   {
      return true;
   }

   /**
    * Initializes the <code>FilePolicyModule</code> by loading
    * the policies contained in the collection of files associated
    * with this module. This method also uses the specified 
    * <code>PolicyFinder</code> to help in instantiating PolicySets.
    *
    * @param finder a PolicyFinder used to help in instantiating PolicySets
    */
   public void init(PolicyFinder finder)
   {
      PolicyReader reader = new PolicyReader(finder, logger, schemaFile);

      Iterator it = fileNames.iterator();
      while (it.hasNext())
      {
         String fname = (String) (it.next());
         try
         {
            AbstractPolicy policy = reader.readPolicy(new FileInputStream(fname));
            policies.addPolicy(policy);
         }
         catch (FileNotFoundException fnfe)
         {
            if (logger.isLoggable(Level.WARNING))
               logger.log(Level.WARNING, "File couldn't be read: " + fname, fnfe);
         }
         catch (ParsingException pe)
         {
            if (logger.isLoggable(Level.WARNING))
               logger.log(Level.WARNING, "Error reading policy from file " + fname, pe);
         }
      }
   }

   /**
    * Finds a policy based on a request's context. If more than one
    * applicable policy is found, this will return an error. Note that
    * this is basically just a subset of the OnlyOneApplicable Policy
    * Combining Alg that skips the evaluation step. See comments in there
    * for details on this algorithm.
    *
    * @param context the representation of the request data
    *
    * @return the result of trying to find an applicable policy
    */
   public PolicyFinderResult findPolicy(EvaluationCtx context)
   {
      try
      {
         AbstractPolicy policy = policies.getPolicy(context);
         if (policy == null)
            return new PolicyFinderResult();
         else
            return new PolicyFinderResult(policy);
      }
      catch (TopLevelPolicyException tlpe)
      {
         return new PolicyFinderResult(tlpe.getStatus());
      }
   }

}
